/*
 * Copyright (C) 2015 Apple Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
 */

#pragma once

#include "DirectArgumentsOffset.h"
#include "ScopeOffset.h"
#include "include/JavaScriptCore/bytecode/VirtualRegister.h"
#include <wtf/HashMap.h>

namespace JSC {

    enum class VarKind : uint8_t {
        Invalid,
        Scope,
        Stack,
        DirectArgument
    };

    class VarOffset {
    public:
        VarOffset()
                : m_kind(VarKind::Invalid), m_offset(UINT_MAX) {
        }

        VarOffset(WTF::HashTableDeletedValueType)
                : m_kind(VarKind::Invalid), m_offset(0) {
        }

        explicit VarOffset(VirtualRegister stackOffset) {
            if (!stackOffset.isValid()) {
                m_kind = VarKind::Invalid;
                m_offset = UINT_MAX;
            } else {
                m_kind = VarKind::Stack;
                m_offset = stackOffset.offset();
            }
        }

        explicit VarOffset(ScopeOffset scopeOffset) {
            if (!scopeOffset) {
                m_kind = VarKind::Invalid;
                m_offset = UINT_MAX;
            } else {
                m_kind = VarKind::Scope;
                m_offset = scopeOffset.offset();
            }
        }

        explicit VarOffset(DirectArgumentsOffset capturedArgumentsOffset) {
            if (!capturedArgumentsOffset) {
                m_kind = VarKind::Invalid;
                m_offset = UINT_MAX;
            } else {
                m_kind = VarKind::DirectArgument;
                m_offset = capturedArgumentsOffset.offset();
            }
        }

        static VarOffset assemble(VarKind kind, unsigned offset) {
            VarOffset result;
            result.m_kind = kind;
            result.m_offset = offset;
            result.checkSanity();
            return result;
        }

        bool isValid() const {
            return m_kind != VarKind::Invalid;
        }

        bool operator!() const {
            return !isValid();
        }

        VarKind kind() const { return m_kind; }

        bool isStack() const {
            return m_kind == VarKind::Stack;
        }

        bool isScope() const {
            return m_kind == VarKind::Scope;
        }

        bool isDirectArgument() const {
            return m_kind == VarKind::DirectArgument;
        }

        VirtualRegister stackOffsetUnchecked() const {
            if (!isStack())
                return VirtualRegister();
            return VirtualRegister(m_offset);
        }

        ScopeOffset scopeOffsetUnchecked() const {
            if (!isScope())
                return ScopeOffset();
            return ScopeOffset(m_offset);
        }

        DirectArgumentsOffset capturedArgumentsOffsetUnchecked() const {
            if (!isDirectArgument())
                return DirectArgumentsOffset();
            return DirectArgumentsOffset(m_offset);
        }

        VirtualRegister stackOffset() const {
            ASSERT(isStack());
            return VirtualRegister(m_offset);
        }

        ScopeOffset scopeOffset() const {
            ASSERT(isScope());
            return ScopeOffset(m_offset);
        }

        DirectArgumentsOffset capturedArgumentsOffset() const {
            ASSERT(isDirectArgument());
            return DirectArgumentsOffset(m_offset);
        }

        unsigned rawOffset() const {
            ASSERT(isValid());
            return m_offset;
        }

        void checkSanity() const {
            if (ASSERT_DISABLED)
                return;

            switch (m_kind) {
                case VarKind::Invalid:
                    ASSERT(m_offset == UINT_MAX);
                    return;
                case VarKind::Scope:
                    ASSERT(scopeOffset());
                    return;
                case VarKind::Stack:
                    ASSERT(stackOffset().isValid());
                    return;
                case VarKind::DirectArgument:
                    ASSERT(capturedArgumentsOffset());
                    return;
            }

            ASSERT_NOT_REACHED();
        }

        bool operator==(const VarOffset &other) const {
            return m_kind == other.m_kind
                   && m_offset == other.m_offset;
        }

        bool operator!=(const VarOffset &other) const {
            return !(*this == other);
        }

        unsigned hash() const {
            return WTF::IntHash<unsigned>::hash((static_cast<unsigned>(m_kind) << 20) + m_offset);
        }

        bool isHashTableDeletedValue() const {
            return m_kind == VarKind::Invalid && !m_offset;
        }

        void dump(PrintStream &) const;

    private:
        VarKind m_kind;
        unsigned m_offset;
    };

    struct VarOffsetHash {
        static unsigned hash(const VarOffset &key) { return key.hash(); }

        static bool equal(const VarOffset &a, const VarOffset &b) { return a == b; }

        static const bool safeToCompareToEmptyOrDeleted = true;
    };

} // namespace JSC

namespace WTF {

    void printInternal(PrintStream &, JSC::VarKind);

    template<typename T>
    struct DefaultHash;
    template<>
    struct DefaultHash<JSC::VarOffset> {
        typedef JSC::VarOffsetHash Hash;
    };

    template<typename T>
    struct HashTraits;
    template<>
    struct HashTraits<JSC::VarOffset> : SimpleClassHashTraits<JSC::VarOffset> {
        static const bool emptyValueIsZero = false;
    };

} // namespace WTF
